/////////////////////////////////////////////////////////////////////
//
//  MOE Framework (Meta Object Encapsulation Framework)
//
//  Original Author: Wiksy Wang, 2011
//
/////////////////////////////////////////////////////////////////////

#ifndef MOE_PERSIST_PROPERTY_TREE_READER_HPP
#define MOE_PERSIST_PROPERTY_TREE_READER_HPP

#include <boost/scoped_array.hpp>
#include <boost/property_tree/ptree_fwd.hpp>

#include "PersistCommon.h"
#include "DefaultNamespaceTreeFormat.hpp"
#include "../ClassMeta.h"
#include "../CommonDef.h"
#include "../DataMemberMeta.h"
#include "../ObjectTypeManager.h"
#include "../Name/NamedObjectCacheManager.h"
#include "../Name/Namespace.h"

namespace MOE
{

namespace Persist
{

template <class TreeType, template <class> class TreeFormatPolicy = DefaultNamespaceTreeFormat>
class PropertyTreeReader
{
public:
	explicit PropertyTreeReader(Name::Namespace* targetNamespace = NULL)
		: _targetNamespace(targetNamespace)
	{
		if (_targetNamespace == NULL)
		{
			_targetNamespace = &Name::Namespace::GetGlobalNamespace();
		}
	}

	bool Read(const ClassMeta& meta, const TreeType& ptree, void* outData) const
	{
		if (meta.IsAtomClass())
		{
			return ReadAtom(meta, ptree, outData);
		}

		bool readResult = true;
		for (TreeType::const_iterator it = ptree.begin(), itEnd = ptree.end(); it != itEnd; ++it)
		{
			const DataMemberMeta& member = meta.Member(it->first);
			if (!member.IsValid())
			{
				continue;
			}

			ClassMetaProxy metaProxy = ObjectTypeManager::GetSingleton().GetClassMeta(member.GetTypeID());
			if (!metaProxy.IsValid())
			{
				continue;
			}
			const ClassMeta& meta = metaProxy;

			switch (member.GetDerivedType())
			{
			case DT_Value:
				readResult &= ReadValue(it->second, outData, member, meta);
				break;
			case DT_Pointer:
				readResult &= ReadPointer(it->second, outData, member, meta);
				break;
			default:
				readResult = false;
			}
		}

		return readResult;
	}

protected:
	bool ReadValue(const TreeType& ptree, void* outData, const DataMemberMeta& member, const ClassMeta& memberMeta) const
	{
		bool readResult = false;

		if (member.HasDirectAccess())
		{
			readResult = Read(memberMeta, ptree, member.DirectAccess(outData));
		}
		else if (member.HasSetter())
		{
			Address tempData = memberMeta.New();
			readResult = Read(memberMeta, ptree, tempData);
			member.Set(outData, tempData);
			memberMeta.Delete(tempData);
		}

		return readResult;
	}

	bool ReadPointer(const TreeType& ptree, void* outData, const DataMemberMeta& member, const ClassMeta& memberMeta) const
	{
		const TreeType::data_type& objName = ptree.data();
		const Name::NamePath objNamePath(objName);
		Address objPtr = _targetNamespace->GetObject(objNamePath, member.GetTypeID());
		if (!objPtr)
		{
			// Register handler in object cache manager.
			if (member.HasDirectAccess() || member.HasSetter())
			{
				Name::NamedObjectCacheManager::GetSingleton().RegisterHandler(_targetNamespace->GetChildFullPath(objNamePath), NameReadHandler(outData, member));
				return true;
			}
			else
			{
				return false;
			}
		}
		else
		{
			if (member.HasDirectAccess())
			{
				*static_cast<Address*>(member.DirectAccess(outData)) = objPtr;
				return true;
			}
			else if (member.HasSetter())
			{
				member.Set(outData, &objPtr);
				return true;
			}
			else
			{
				return false;
			}
		}
	}

	bool ReadAtom(const ClassMeta& meta, const TreeType& ptree, void* outData) const
	{
		switch (meta.GetAtomType())
		{
		case AT_Integer:
			*static_cast<int*>(outData) = ptree.get_value<int>();
			return true;
		case AT_Float:
			*static_cast<float*>(outData) = ptree.get_value<float>();
			return true;
		case AT_Bool:
			*static_cast<bool*>(outData) = ptree.get_value<bool>();
			return true;
		case AT_String:
			{
				TreeType::data_type tmpStr = ptree.get_value<TreeType::data_type>();
				static_cast<std::string*>(outData)->assign(tmpStr.begin(), tmpStr.end());
			}
			return true;
		case AT_WideString:
			{
				TreeType::data_type tmpStr = ptree.get_value<TreeType::data_type>();
				static_cast<std::wstring*>(outData)->assign(tmpStr.begin(), tmpStr.end());
			}
			return true;
		case AT_NonAtom:
		default:
			return false;
		}
	}

protected:
	Name::Namespace* _targetNamespace;
};

}

}

#endif
